#include "ext2.h"
#include "disk.h"
#include "head.h"
#include "file_system.h"
#include<string.h>
#include<stdio.h>
#include<stdlib.h>

#define SP_BLOCK_INDEX 0
#define ROOT_INODE_INDEX 2
#define MAGIC_NUM 0xc3de
#define FILE_SYSTEM_BLOCK_SIZE DEVICE_BLOCK_SIZE*2
#define INODE_MAP_NUM 32
#define BLOCK_MAP_NUM 32

static const sp_block sp_block_init = {
    .magic_num = MAGIC_NUM,
    .free_block_count = 4063,
    .free_inode_count = 1024,
    .dir_inode_count = 0
};

char buf[FILE_SYSTEM_BLOCK_SIZE];
char dest[100], src[100];


int ls(char *argv[], int *argc){
    printf("ls...\n");
    if(*argc > 2 || strcmp(argv[0], "ls") != 0){
        return -1;
    }
    struct inode *current_inode = NULL;
    int inode_id = 0;
    if(*argc == 2){
        parse_path(argv[1], argv, argc);
        current_inode = get_inode_by_path(argv, *argc, &inode_id);
        if(current_inode == NULL)
            return -1;
    }
    else
    {
        current_inode = file_system_read_inode(ROOT_INODE_INDEX, buf);
    }
    printf(".\n..\n");
    for(int i=0;i < 6;i++){
        if(current_inode->block_point[i] != 0xffffffff){
            file_system_read_data(current_inode->block_point[i], buf);
            struct dir_item *p = (struct dir_item*) buf;
            for(int j=0;j < 8;j++){
                if(p->valid){
                    printf("%s\n", p->name);
                }
                p++;
            }
        }
    }
    free(current_inode);
}

int mkdir(char *argv[], int *argc){
    printf("mkdir..\n");
    if(*argc != 2 || strcmp(argv[0], "mkdir") != 0){
        printf("Wrong arguments!\n");
        return -1;
    }
    if(parse_path(argv[1], argv, argc)){
        printf("Wrong arguments!\n");
        return -1;
    }
    int i = 0;
    int num = *argc;
    int inode_id = ROOT_INODE_INDEX;
    struct inode *current_inode = NULL;
    if(num > 1){
        current_inode = get_inode_by_path(argv, num - 1, &inode_id);
        if(current_inode == NULL)
            return -1;
    }
    else
    {
        current_inode = file_system_read_inode(ROOT_INODE_INDEX, buf);
    }
    for(int j=0;j < 6;j++){
        if(current_inode->block_point[j] != 0xffffffff){ //当前block_point已分配        
            if(check_block_point(current_inode->block_point[j]))
                continue;
            make_new_dir_by_block_index_and_name(current_inode->block_point[j], argv[num-1], TYPE_DIR);
            file_system_write_inode(inode_id, current_inode, buf);
            free(current_inode);
            return 0;
        }
        else
        {
            current_inode->block_point[j] = find_first_free_data();
            set_sp_block_data(current_inode->block_point[j]);
            init_block_by_index(current_inode->block_point[j]);
            make_new_dir_by_block_index_and_name(current_inode->block_point[j], argv[num-1], TYPE_DIR);
            file_system_write_inode(inode_id, current_inode, buf);
            free(current_inode);
            return 0;
        }
    }
    return -1;
}

int touch(char *argv[], int *argc){
    printf("touch..\n");
    if(*argc != 2 || strcmp(argv[0], "touch") != 0){
        return -1;
    }
    if(*(argv[1] + strlen(argv[1]) - 1) == '/'){
        printf("Wrong arguments!\n");
        return -1;
    }
    if(parse_path(argv[1], argv, argc)){
        printf("Wrong arguments!\n");
        return -1;
    }
    int i = 0;
    int num = *argc;
    int inode_id = ROOT_INODE_INDEX;
    struct inode *current_inode = NULL;
    if(num > 1){
        current_inode = get_inode_by_path(argv, num - 1, &inode_id);
        if(current_inode == NULL)
            return -1;
    }
    else
    {
        current_inode = file_system_read_inode(ROOT_INODE_INDEX, buf);
    }
    for(int j=0;j < 6;j++){
        if(current_inode->block_point[j] != 0xffffffff){ //当前block_point已分配        
            if(check_block_point(current_inode->block_point[j]))
                continue;
            make_new_dir_by_block_index_and_name(current_inode->block_point[j], argv[num-1], TYPE_FILE);
            file_system_write_inode(inode_id, current_inode, buf);
            free(current_inode);
            return 0;
        }
        else
        {
            current_inode->block_point[j] = find_first_free_data();
            set_sp_block_data(current_inode->block_point[j]);
            init_block_by_index(current_inode->block_point[j]);
            make_new_dir_by_block_index_and_name(current_inode->block_point[j], argv[num-1], TYPE_FILE);
            file_system_write_inode(inode_id, current_inode, buf);
            free(current_inode);
            return 0;
        }
    }
    return -1;
}

int cp(char *argv[], int *argc){
    if(*argc != 3 || strcmp(argv[0], "cp") != 0){
        printf("Wrong arguments!\n");
        return -1;
    }
    if(*(argv[1] + strlen(argv[1]) - 1) == '/' || *(argv[2] + strlen(argv[2]) - 1) == '/'){
        printf("Wrong arguments!\n");
        return -1;
    }
    strcpy(src, argv[1]);
    strcpy(dest, argv[2]);
    int inode_id = 0;
    
    if(parse_path(src, argv, argc)){
        printf("Wrong arguments!\n");
        return -1;
    }
    struct inode *src_pre = NULL;
    if((*argc) > 1){
        src_pre = get_inode_by_path(argv, (*argc) - 1,&inode_id);
        if(src_pre == NULL)
            return -1;
    }
    else
    {
        src_pre = file_system_read_inode(ROOT_INODE_INDEX, buf);
    }
    int src_id = get_inode_id_by_inode_and_dir_name(src_pre, argv[(*argc) - 1], TYPE_FILE);
    free(src_pre);
    if(src_id == -1){
        printf("No file named %s\n", argv[(*argc) - 1]);
        return -1;
    }
    struct inode *src_inode = file_system_read_inode(src_id, buf);
    

    if(parse_path(dest, argv, argc)){
        printf("Wrong arguments!\n");
        return -1;
    }
    struct inode *dest_pre = NULL;
    if((*argc) > 1){
        dest_pre = get_inode_by_path(argv, (*argc) - 1,&inode_id);
        if(dest_pre == NULL)
            return -1;
    }
    else
    {
        inode_id = ROOT_INODE_INDEX;
        dest_pre = file_system_read_inode(ROOT_INODE_INDEX, buf);
    }
    for(int j=0;j < 6;j++){
        if(dest_pre->block_point[j] != 0xffffffff){ //当前block_point已分配        
            if(check_block_point(dest_pre->block_point[j]))
                continue;
            create_copied_dir_by_block_index_and_name(dest_pre->block_point[j], argv[(*argc)-1], src_inode);
            file_system_write_inode(inode_id, dest_pre, buf);
            free(dest_pre);
            free(src_pre);
            return 0;
        }
        else
        {
            dest_pre->block_point[j] = find_first_free_data();
            set_sp_block_data(dest_pre->block_point[j]);
            init_block_by_index(dest_pre->block_point[j]);
            create_copied_dir_by_block_index_and_name(dest_pre->block_point[j], argv[(*argc)-1], src_inode);
            file_system_write_inode(inode_id, dest_pre, buf);
            free(dest_pre);
            free(src_pre);
            return 0;
        }
    }
}

void shutdown(){
    memcpy(buf, (char*)&superblock, FILE_SYSTEM_BLOCK_SIZE);
    file_system_write_super(buf);
    exit(0);
}

int init_ext2(){
    if(open_disk()){    //打开disk
        printf("Open disk failed!");
        return -1;
    }
    if(file_system_read_super(buf)){
        printf("Read super faled!");
        return -1;
    }
    sp_block* p_superblock = (sp_block*) buf;
    if(p_superblock->magic_num != MAGIC_NUM){
        printf("There is no file system yet.Creating a new disc...\n");
        init_sp_block(p_superblock);        
    }
    else{
        char choice;
        printf("There already exists a file system\n");
        printf("Do you want to format disk and rebuild a sucking file system?(y/n)");
        choice = getchar();
        getchar();
        switch (choice)
        {
        case 'y':
            init_sp_block(&superblock);
            break;
        case 'n':
            superblock = *p_superblock;
            printf("The file system is ready\n");
            break;
        default:
            break;
        }
    }
}

void init_sp_block(sp_block* p_superblock){
    p_superblock->magic_num = sp_block_init.magic_num;
    p_superblock->free_block_count = sp_block_init.free_block_count;
    p_superblock->free_inode_count = sp_block_init.free_inode_count;
    p_superblock->dir_inode_count = sp_block_init.dir_inode_count;
    memset(p_superblock->inode_map, 0, 32 * sizeof(uint32_t));
    memset(p_superblock->block_map, 0, 128 * sizeof(uint32_t));

    superblock = *p_superblock;
    
    init_new_inode_by_index_and_type(ROOT_INODE_INDEX, TYPE_DIR);
    printf("New file system is ready.\n");
}

void set_sp_block_inode(int inode_id){
    superblock.free_inode_count--;
    superblock.inode_map[inode_id/32] |= (1 << (inode_id%INODE_MAP_NUM));
}

void clear_sp_block_inode(int inode_id){
    superblock.free_inode_count++;
    superblock.inode_map[inode_id/32] &= ~(1 << (inode_id%INODE_MAP_NUM));
}

void set_sp_block_data(int data_index){
    superblock.free_block_count--;
    superblock.block_map[data_index/32] |= (1 << (data_index%BLOCK_MAP_NUM));
}

void clear_sp_block_data(int data_index){
    superblock.free_block_count++;
    superblock.block_map[data_index/32] &= ~(1 << (data_index%BLOCK_MAP_NUM));
}

int find_first_free_inode(){
    if(superblock.free_inode_count <= 0)
        return -1;
    int inode_id = 0;
    while (inode_id < 1024)
    {
        if((superblock.inode_map[inode_id/32] & (1 << (inode_id%INODE_MAP_NUM))) == 0)
            return inode_id;
        inode_id++;
    }
    return -1;
}

int find_first_free_data(){
    if(superblock.free_block_count <= 0)
        return -1;
    int block_id = 0;
    while (block_id < 4063)
    {
        if((superblock.inode_map[block_id/32] & (1 << (block_id%BLOCK_MAP_NUM))) == 0)
            return block_id;
        block_id++;
    }
    return -1;
}

int parse_path(char *path, char *argv[], int *argc){
    int i,j;
    i = 0;
    if(*path != '/'){
        return -1;
    }
    path++;
    for(j = 0; path[j] != '\0' && path[j] != '\n';j++){
        argv[i++] = path + j;
        while (path[j] != '/' && path[j] != '\0')
        {
            j++;
        }
        path[j] = '\0';
    }
    argv[i] = 0;
    *argc = i;
    return 0;
}

/** 
 * @brief 检查当前block是否还有空间存放dir_item
 * @return 0表示当前block还能存放dir_item
*/
int check_block_point(int data_block_index){
    file_system_read_data(data_block_index, buf);
    struct dir_item *p = (struct dir_item*) buf;
    for(int i=0;i < 8;i++){
        p += i;
        if(p->valid == 0)
            return 0;   //当前block还有空闲区域存放dir_item
    }
    return -1;
}

int make_new_dir_by_block_index_and_name(int block_index, char *name, int type){
    file_system_read_data(block_index, buf);
    struct dir_item *p = (struct dir_item*) buf;
    for(int i=0;i < 8;i++){
        if(p->valid == 0){
            p->valid = 1;
            p->type = type;
            p->inode_id = find_first_free_inode();
            strcpy(p->name, name);

            if(!file_system_write_data(block_index, buf))
                printf("Create %s succeed\n", type == TYPE_FILE ? "file" : "dir");
            init_new_inode_by_index_and_type(p->inode_id, type);
            return 0;
        }
        p++;
    }
    return -1;
}

int create_copied_dir_by_block_index_and_name(int block_index, char *name, struct inode *src){
    file_system_read_data(block_index, buf);
    struct dir_item *p = (struct dir_item*) buf;
    for(int i=0;i < 8;i++){
        if(p->valid == 0){
            p->valid = 1;
            p->type = TYPE_FILE;
            int inode_id = find_first_free_inode();
            p->inode_id = inode_id;
            strcpy(p->name, name);

            if(!file_system_write_data(block_index, buf))
                printf("Copy file succeed\n");

            // copy inode
            struct inode *dest_inode = malloc(sizeof(struct inode));
            set_sp_block_inode(inode_id);
            dest_inode->link = 1;
            dest_inode->file_type = TYPE_FILE;
            dest_inode->size = src->size;
            
            for(int j=0;j < 6;j++){
                if(src->block_point[j] != 0xffffffff){
                    dest_inode->block_point[j] = find_first_free_data();
                    file_system_read_data(src->block_point[j], buf);
                    file_system_write_data(dest_inode->block_point[j], buf);
                }
                else
                    dest_inode->block_point[j] = 0xffffffff;
            }

            file_system_write_inode(inode_id, dest_inode, buf);
            free(dest_inode);
            return 0;
        }
        p++;
    }
    return -1;
}

void init_block_by_index(int block_index){
    memset(buf, 0, FILE_SYSTEM_BLOCK_SIZE);
    file_system_write_data(block_index, buf);
}

int get_inode_id_by_inode_and_dir_name(struct inode *p, char *dir_name, int type){
    for(int j=0;j < 6;j++){
        if(p->block_point[j] != 0xffffffff){ //当前block_point已分配
            file_system_read_data(p->block_point[j], buf);
            struct dir_item *item = (struct dir_item*)buf;
            for(int i=0;i < 8;i++){
                if(item->valid){
                    if(item->type == type && strcmp(dir_name, item->name) == 0){
                        return item->inode_id;
                    }
                }
                item++;
            }
        }             
    }
    return -1;
}

void init_new_inode_by_index_and_type(int inode_id, int type){
    struct inode *p = malloc(sizeof(struct inode));
    set_sp_block_inode(inode_id);
    p->link = 1;
    p->file_type = type;
    memset(p->block_point, 0xff, sizeof(uint32_t) * 6);
    file_system_write_inode(inode_id, p, buf);
    free(p);
}

struct inode* get_inode_by_path(char *argv[], int argc, int *inode_id){
    struct inode* current_inode = file_system_read_inode(ROOT_INODE_INDEX, buf);
    for (int i=0;i < argc;i++)
    {
        *inode_id = get_inode_id_by_inode_and_dir_name(current_inode, argv[i], TYPE_DIR);
        if(*inode_id == -1){
            printf("No dir named %s!\n", argv[i]);
            return NULL;
        }
        free(current_inode);
        current_inode = file_system_read_inode(*inode_id, buf);
    }
    return current_inode;
}